View Javadoc
1   package org.apache.maven.surefire.junit4;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Set;
25  
26  import org.apache.maven.surefire.booter.Command;
27  import org.apache.maven.surefire.booter.MasterProcessListener;
28  import org.apache.maven.surefire.booter.MasterProcessReader;
29  import org.apache.maven.surefire.common.junit4.ClassMethod;
30  import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
31  import org.apache.maven.surefire.common.junit4.JUnit4TestChecker;
32  import org.apache.maven.surefire.common.junit4.JUnitTestFailureListener;
33  import org.apache.maven.surefire.common.junit4.Notifier;
34  import org.apache.maven.surefire.providerapi.AbstractProvider;
35  import org.apache.maven.surefire.providerapi.ProviderParameters;
36  import org.apache.maven.surefire.report.ConsoleOutputReceiver;
37  import org.apache.maven.surefire.report.PojoStackTraceWriter;
38  import org.apache.maven.surefire.report.ReportEntry;
39  import org.apache.maven.surefire.report.ReporterFactory;
40  import org.apache.maven.surefire.report.RunListener;
41  import org.apache.maven.surefire.report.SimpleReportEntry;
42  import org.apache.maven.surefire.suite.RunResult;
43  import org.apache.maven.surefire.testset.TestListResolver;
44  import org.apache.maven.surefire.testset.TestRequest;
45  import org.apache.maven.surefire.testset.TestSetFailedException;
46  import org.apache.maven.surefire.util.RunOrderCalculator;
47  import org.apache.maven.surefire.util.ScanResult;
48  import org.apache.maven.surefire.util.TestsToRun;
49  import org.junit.runner.Description;
50  import org.junit.runner.Result;
51  import org.junit.runner.Runner;
52  import org.junit.runner.manipulation.Filter;
53  import org.junit.runner.notification.StoppedByUserException;
54  
55  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.createSuiteDescription;
56  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.cutTestClassAndMethod;
57  import static org.apache.maven.surefire.common.junit4.JUnit4ProviderUtil.generateFailingTests;
58  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createDescription;
59  import static org.apache.maven.surefire.common.junit4.JUnit4Reflector.createIgnored;
60  import static org.apache.maven.surefire.common.junit4.JUnit4RunListener.rethrowAnyTestMechanismFailures;
61  import static org.apache.maven.surefire.common.junit4.JUnit4RunListenerFactory.createCustomListeners;
62  import static org.apache.maven.surefire.report.ConsoleOutputCapture.startCapture;
63  import static org.apache.maven.surefire.report.SimpleReportEntry.withException;
64  import static org.apache.maven.surefire.testset.TestListResolver.toClassFileName;
65  import static org.apache.maven.surefire.util.TestsToRun.fromClass;
66  import static org.junit.runner.Request.aClass;
67  import static org.junit.runner.Request.method;
68  import static java.lang.reflect.Modifier.isAbstract;
69  import static java.lang.reflect.Modifier.isInterface;
70  import static java.util.Collections.unmodifiableCollection;
71  
72  /**
73   * @author Kristian Rosenvold
74   */
75  public class JUnit4Provider
76      extends AbstractProvider
77  {
78      private static final String UNDETERMINED_TESTS_DESCRIPTION = "cannot determine test in forked JVM with surefire";
79  
80      private final ClassLoader testClassLoader;
81  
82      private final Collection<org.junit.runner.notification.RunListener> customRunListeners;
83  
84      private final JUnit4TestChecker jUnit4TestChecker;
85  
86      private final TestListResolver testResolver;
87  
88      private final ProviderParameters providerParameters;
89  
90      private final RunOrderCalculator runOrderCalculator;
91  
92      private final ScanResult scanResult;
93  
94      private final int rerunFailingTestsCount;
95  
96      private final MasterProcessReader commandsReader;
97  
98      private TestsToRun testsToRun;
99  
100     public JUnit4Provider( ProviderParameters booterParameters )
101     {
102         // don't start a thread in MasterProcessReader while we are in in-plugin process
103         commandsReader = booterParameters.isInsideFork()
104             ? MasterProcessReader.getReader().setShutdown( booterParameters.getShutdown() )
105             : null;
106         providerParameters = booterParameters;
107         testClassLoader = booterParameters.getTestClassLoader();
108         scanResult = booterParameters.getScanResult();
109         runOrderCalculator = booterParameters.getRunOrderCalculator();
110         String listeners = booterParameters.getProviderProperties().get( "listener" );
111         customRunListeners = unmodifiableCollection( createCustomListeners( listeners ) );
112         jUnit4TestChecker = new JUnit4TestChecker( testClassLoader );
113         TestRequest testRequest = booterParameters.getTestRequest();
114         testResolver = testRequest.getTestListResolver();
115         rerunFailingTestsCount = testRequest.getRerunFailingTestsCount();
116     }
117 
118     public RunResult invoke( Object forkTestSet )
119         throws TestSetFailedException
120     {
121         if ( isRerunFailingTests() && isFailFast() )
122         {
123             throw new TestSetFailedException( "don't enable parameters rerunFailingTestsCount, skipAfterFailureCount" );
124         }
125 
126         if ( testsToRun == null )
127         {
128             if ( forkTestSet instanceof TestsToRun )
129             {
130                 testsToRun = (TestsToRun) forkTestSet;
131             }
132             else if ( forkTestSet instanceof Class )
133             {
134                 testsToRun = fromClass( (Class<?>) forkTestSet );
135             }
136             else
137             {
138                 testsToRun = scanClassPath();
139             }
140         }
141 
142         upgradeCheck();
143 
144         ReporterFactory reporterFactory = providerParameters.getReporterFactory();
145 
146         RunListener reporter = reporterFactory.createReporter();
147 
148         startCapture( (ConsoleOutputReceiver) reporter );
149 
150         Notifier notifier = new Notifier( new JUnit4RunListener( reporter ), getSkipAfterFailureCount() );
151         if ( isFailFast() )
152         {
153             notifier.addListener( new JUnit4FailFastListener( notifier ) );
154         }
155         Result result = new Result();
156         notifier.addListeners( customRunListeners )
157             .addListener( result.createListener() );
158 
159         if ( isFailFast() && commandsReader != null )
160         {
161             registerPleaseStopJunitListener( notifier );
162         }
163 
164         try
165         {
166             notifier.fireTestRunStarted( testsToRun.allowEagerReading()
167                                                 ? createTestsDescription()
168                                                 : createDescription( UNDETERMINED_TESTS_DESCRIPTION ) );
169 
170             if ( commandsReader != null )
171             {
172                 commandsReader.addShutdownListener( new MasterProcessListener()
173                 {
174                     public void update( Command command )
175                     {
176                         testsToRun.markTestSetFinished();
177                     }
178                 } );
179                 commandsReader.awaitStarted();
180             }
181 
182             for ( Class<?> aTestsToRun : testsToRun )
183             {
184                 executeTestSet( aTestsToRun, reporter, notifier );
185             }
186         }
187         finally
188         {
189             notifier.fireTestRunFinished( result );
190             notifier.removeListeners();
191             closeCommandsReader();
192         }
193 
194         rethrowAnyTestMechanismFailures( result );
195         return reporterFactory.close();
196     }
197 
198     private boolean isRerunFailingTests()
199     {
200         return rerunFailingTestsCount > 0;
201     }
202 
203     private boolean isFailFast()
204     {
205         return providerParameters.getSkipAfterFailureCount() > 0;
206     }
207 
208     private int getSkipAfterFailureCount()
209     {
210         return isFailFast() && !isRerunFailingTests() ? providerParameters.getSkipAfterFailureCount() : 0;
211     }
212 
213     private void closeCommandsReader()
214     {
215         if ( commandsReader != null )
216         {
217             commandsReader.stop();
218         }
219     }
220 
221     private MasterProcessListener registerPleaseStopJunitListener( final Notifier notifier )
222     {
223         MasterProcessListener listener = new MasterProcessListener()
224         {
225             public void update( Command command )
226             {
227                 notifier.pleaseStop();
228             }
229         };
230         commandsReader.addSkipNextListener( listener );
231         return listener;
232     }
233 
234     private void executeTestSet( Class<?> clazz, RunListener reporter, Notifier notifier )
235     {
236         final ReportEntry report = new SimpleReportEntry( getClass().getName(), clazz.getName() );
237         reporter.testSetStarting( report );
238         try
239         {
240             executeWithRerun( clazz, notifier );
241         }
242         catch ( Throwable e )
243         {
244             if ( isFailFast() && e instanceof StoppedByUserException )
245             {
246                 String reason = e.getClass().getName();
247                 Description skippedTest = createDescription( clazz.getName(), createIgnored( reason ) );
248                 notifier.fireTestIgnored( skippedTest );
249             }
250             else
251             {
252                 String reportName = report.getName();
253                 String reportSourceName = report.getSourceName();
254                 PojoStackTraceWriter stackWriter = new PojoStackTraceWriter( reportSourceName, reportName, e );
255                 reporter.testError( withException( reportSourceName, reportName, stackWriter ) );
256             }
257         }
258         finally
259         {
260             reporter.testSetCompleted( report );
261         }
262     }
263 
264     private void executeWithRerun( Class<?> clazz, Notifier notifier ) throws TestSetFailedException
265     {
266         JUnitTestFailureListener failureListener = new JUnitTestFailureListener();
267         notifier.addListener( failureListener );
268         boolean hasMethodFilter = testResolver != null && testResolver.hasMethodPatterns();
269         execute( clazz, notifier, hasMethodFilter ? new TestResolverFilter() : new NullFilter() );
270 
271         // Rerun failing tests if rerunFailingTestsCount is larger than 0
272         if ( isRerunFailingTests() )
273         {
274             for ( int i = 0; i < rerunFailingTestsCount && !failureListener.getAllFailures().isEmpty(); i++ )
275             {
276                 Set<ClassMethod> failedTests = generateFailingTests( failureListener.getAllFailures() );
277                 failureListener.reset();
278                 if ( !failedTests.isEmpty() )
279                 {
280                     executeFailedMethod( notifier, failedTests );
281                 }
282             }
283         }
284     }
285 
286     public Iterable<Class<?>> getSuites()
287     {
288         testsToRun = scanClassPath();
289         return testsToRun;
290     }
291 
292     private TestsToRun scanClassPath()
293     {
294         final TestsToRun scannedClasses = scanResult.applyFilter( jUnit4TestChecker, testClassLoader );
295         return runOrderCalculator.orderTestClasses( scannedClasses );
296     }
297 
298     private void upgradeCheck()
299         throws TestSetFailedException
300     {
301         if ( isJUnit4UpgradeCheck() )
302         {
303             Collection<Class<?>> classesSkippedByValidation =
304                 scanResult.getClassesSkippedByValidation( jUnit4TestChecker, testClassLoader );
305             if ( !classesSkippedByValidation.isEmpty() )
306             {
307                 StringBuilder reason = new StringBuilder();
308                 reason.append( "Updated check failed\n" );
309                 reason.append( "There are tests that would be run with junit4 / surefire 2.6 but not with [2.7,):\n" );
310                 for ( Class testClass : classesSkippedByValidation )
311                 {
312                     reason.append( "   " );
313                     reason.append( testClass.getName() );
314                     reason.append( "\n" );
315                 }
316                 throw new TestSetFailedException( reason.toString() );
317             }
318         }
319     }
320 
321     private Description createTestsDescription()
322     {
323         Collection<Class<?>> classes = new ArrayList<Class<?>>();
324         for ( Class<?> clazz : testsToRun )
325         {
326             classes.add( clazz );
327         }
328         return createSuiteDescription( classes );
329     }
330 
331     private static boolean isJUnit4UpgradeCheck()
332     {
333         return System.getProperty( "surefire.junit4.upgradecheck" ) != null;
334     }
335 
336     private static void execute( Class<?> testClass, Notifier notifier, Filter filter )
337     {
338         final int classModifiers = testClass.getModifiers();
339         if ( !isAbstract( classModifiers ) && !isInterface( classModifiers ) )
340         {
341             Runner runner = aClass( testClass ).filterWith( filter ).getRunner();
342             if ( countTestsInRunner( runner.getDescription() ) != 0 )
343             {
344                 runner.run( notifier );
345             }
346         }
347     }
348 
349     private void executeFailedMethod( Notifier notifier, Set<ClassMethod> failedMethods )
350         throws TestSetFailedException
351     {
352         for ( ClassMethod failedMethod : failedMethods )
353         {
354             try
355             {
356                 Class<?> methodClass = Class.forName( failedMethod.getClazz(), true, testClassLoader );
357                 String methodName = failedMethod.getMethod();
358                 method( methodClass, methodName ).getRunner().run( notifier );
359             }
360             catch ( ClassNotFoundException e )
361             {
362                 throw new TestSetFailedException( "Unable to create test class '" + failedMethod.getClazz() + "'", e );
363             }
364         }
365     }
366 
367     /**
368      * JUnit error: test count includes one test-class as a suite which has filtered out all children.
369      * Then the child test has a description "initializationError0(org.junit.runner.manipulation.Filter)"
370      * for JUnit 4.0 or "initializationError(org.junit.runner.manipulation.Filter)" for JUnit 4.12
371      * and Description#isTest() returns true, but this description is not a real test
372      * and therefore it should not be included in the entire test count.
373      */
374     private static int countTestsInRunner( Description description )
375     {
376         if ( description.isSuite() )
377         {
378             int count = 0;
379             for ( Description child : description.getChildren() )
380             {
381                 if ( !hasFilteredOutAllChildren( child ) )
382                 {
383                     count += countTestsInRunner( child );
384                 }
385             }
386             return count;
387         }
388         else if ( description.isTest() )
389         {
390             return hasFilteredOutAllChildren( description ) ? 0 : 1;
391         }
392         else
393         {
394             return 0;
395         }
396     }
397 
398     private static boolean hasFilteredOutAllChildren( Description description )
399     {
400         String name = description.getDisplayName();
401         // JUnit 4.0: initializationError0; JUnit 4.12: initializationError.
402         if ( name == null )
403         {
404             return true;
405         }
406         else
407         {
408             name = name.trim();
409             return name.startsWith( "initializationError0(org.junit.runner.manipulation.Filter)" )
410                 || name.startsWith( "initializationError(org.junit.runner.manipulation.Filter)" );
411         }
412     }
413 
414     private class TestResolverFilter
415         extends Filter
416     {
417         private final TestListResolver methodFilter = JUnit4Provider.this.testResolver.createMethodFilters();
418 
419         @Override
420         public boolean shouldRun( Description description )
421         {
422             // class: Java class name; method: 1. "testMethod" or 2. "testMethod[5+whatever]" in @Parameterized
423             final ClassMethod cm = cutTestClassAndMethod( description );
424             final boolean isSuite = description.isSuite();
425             final boolean isValidTest = description.isTest() && cm.isValid();
426             final String clazz = cm.getClazz();
427             final String method = cm.getMethod();
428             return isSuite || isValidTest && methodFilter.shouldRun( toClassFileName( clazz ), method );
429         }
430 
431         @Override
432         public String describe()
433         {
434             return methodFilter.toString();
435         }
436     }
437 
438     private final class NullFilter
439         extends TestResolverFilter
440     {
441 
442         @Override
443         public boolean shouldRun( Description description )
444         {
445             return true;
446         }
447 
448         @Override
449         public String describe()
450         {
451             return "";
452         }
453     }
454 }